/*
 * Hubeny
 * Lat,Lng -> X,Y(-1.0~+1.0)
 */
package com.hackathon;

/**
 * @author kitamura
 */
public class Hubeny {

	private static final double a = 6378137D;
	private static final double f = 1D / 298.257222101D;
	private Point point[];

	private static Location2 myLocation;

	public Hubeny() {
		// myLocation = new Location2();
	}

	/**
	 * Hubenyがもつ座標を設定する
	 * 
	 * @deprecated Hubenyが座標を持つのは不適切。executeで直接座標を与えること。
	 * @param lat
	 * @param lng
	 */
	 public static void setMyLocation( double lat, double lng ) {
		 myLocation.setLat( lat );
		 myLocation.setLng( lng );
	 }

	/**
	 * Location2型の2つの地点の距離を返す
	 * 
	 * @param locMy 
	 * @param locBall
	 * @return たぶんメートル
	 */
	public static double getDistance(Location2 locMy, Location2 locBall) {

		double radLatStart = locMy.getLat() * Math.PI / 180D;
		double radLonStart = locMy.getLng() * Math.PI / 180D;
		double radLatEnd = locBall.getLat() * Math.PI / 180D;
		double radLonEnd = locBall.getLng() * Math.PI / 180D;

		double avgLat = (radLatStart + radLatEnd) / 2D;

		double F = 1D / f;

		double e = (Math.sqrt(2 * F - 1)) / F;

		double W = Math.sqrt(1 - Math.pow(e, 2) * Math.pow(Math.sin(avgLat), 2));

		double M = (a * (1 - Math.pow(e, 2))) / Math.pow(W, 3);

		double N = a / W;

		double dLat = radLatStart - radLatEnd;

		double dLon = radLonStart - radLonEnd;

		double d = Math.sqrt(Math.pow(M * dLat, 2) + Math.pow(N * Math.cos(avgLat) * dLon, 2));

		return d;
	}

	/**
	 * LocMyからLocBallへの方向を返す。
	 * 
	 * @param locMy 基準となる座標
	 * @param locBall 目標となる座標
	 * @return 東を0度とした反時計回りの座標系
	 */
	public static double getDirection(Location2 locMy, Location2 locBall) {
		double Lon1Rad, Lat1Rad, Lon2Rad, Lat2Rad;
		double X, Y;
		double LonDiff;
		double Direction;

		Lon1Rad = locMy.getLat() * Math.PI / 180D;
		Lat1Rad = locMy.getLng() * Math.PI / 180D;
		Lon2Rad = locBall.getLat() * Math.PI / 180D;
		Lat2Rad = locBall.getLng() * Math.PI / 180D;
		LonDiff = Lon2Rad - Lon1Rad;
		Y = Math.cos(Lat2Rad) * Math.sin(LonDiff);
		X = Math.cos(Lat1Rad) * Math.sin(Lat2Rad) - Math.sin(Lat1Rad) * Math.cos(Lat2Rad) * Math.cos(LonDiff);
		Direction = Math.atan2(Y, X);
		Direction = Direction * 180D / Math.PI;

		if (Direction < 0) {
			Direction = Direction + 360;
		}
		
		return Direction;
	}

	/**
	 * DragonDataの位置を現在位置からの相対座標を作成する
	 * 
	 * @param 現在位置
	 * @return 現在位置からDragonDataへの相対座標の配列
	 */
	public Point[] execute(Location2 myLocation) {
		double r, theta;// angle is degree
		double r_max = 0.0;

		Location2 ballLocations[];
		DragonData data = DragonData.getInstance();
		ballLocations = data.getBallLocations();

		point = new Point[DragonData.NUM_BALL];

		for (int i = 0; i < DragonData.NUM_BALL; i++) {
			r = Hubeny.getDistance(myLocation, ballLocations[i]);

			// 最も遠い座標を保持する
			if (r_max < r) {
				r_max = r;
			}

			theta = Hubeny.getDirection(myLocation, ballLocations[i]);
			point[i] = new Point(r * Math.cos(theta), r * Math.sin(theta));
		}

		// 現在位置から最も遠い座標を元に座標を正規化する
		for (int i = 0; i < DragonData.NUM_BALL; i++) {
			point[i].normalizePoint(r_max);
		}

		return point;

	}

	/**
	 * DragonDataの位置を現在位置からの相対座標を作成する
	 * 
	 * @deprecated 推奨していません
	 * @return 現在位置からDragonDataへの相対座標の配列
	 */
	public Point[] execute() {
		double r, theta;// angle is degree
		double r_max = 0.0;

		Location2 myLocation = new Location2();
		myLocation.setLat(35.626294);
		myLocation.setLng(139.742062);

		Location2 ballLocations[];
		DragonData data = DragonData.getInstance();
		ballLocations = data.getBallLocations();

		point = new Point[DragonData.NUM_BALL];

		for (int i = 0; i < DragonData.NUM_BALL; i++) {
			r = Hubeny.getDistance(myLocation, ballLocations[i]);

			// 最も遠い座標を保持する
			if (r_max < r) {
				r_max = r;
			}

			theta = Hubeny.getDirection(myLocation, ballLocations[i]);
			point[i] = new Point(r * Math.cos(theta), r * Math.sin(theta));
		}

		// 現在位置から最も遠い座標を元に座標を正規化する
		for (int i = 0; i < DragonData.NUM_BALL; i++) {
			point[i].normalizePoint(r_max);
		}

		return point;

	}
}
